#pragma once

#include <ctime>
#include <sys/time.h>
#include <string>
#include <stdexcept>


namespace evm {

using namespace std;

struct Date {
    /* Date constructed from current date. */
    Date() {
        gettimeofday(&tv, nullptr);
    }

    Date(double time) {
        tv.tv_sec = static_cast<time_t>(time);
        tv.tv_usec = static_cast<suseconds_t>((time - tv.tv_sec) * 1e6);
    }

    /* Date constructed from date string. */
    Date(const string& date_str) {
        struct tm tm;
        auto pos = strptime(date_str.data(), DATE_FORMAT, &tm);
        if (!pos)
            throw invalid_argument("invalid date string");
        tv.tv_sec = mktime(&tm);
        if (static_cast<size_t>(pos - &date_str[0]) < date_str.size())
            tv.tv_usec = std::stod(string(pos)) * 1e6;
        else
            tv.tv_usec = 0;
    }

    Date(const Date&) = default;
    Date(Date&&) noexcept = default;
    Date& operator=(const Date&) = default;
    Date& operator=(Date&&) = default;

    const string& operator()(bool subsecond = true) const {
        if (str.empty()) {
            struct tm tm;
            localtime_r(&tv.tv_sec, &tm);
            char buf[MAX_LEN];
            strftime(buf, MAX_LEN, DATE_FORMAT, &tm);
            if (subsecond)
                str = buf + std::to_string(tv.tv_usec * 1.0e-6).substr(1);
            else
                str = buf;
        }
        return str;
    }

    operator const string&() const {
        return operator()();
    }

private:
    static constexpr const char *DATE_FORMAT = "%Y-%m-%d %H:%M:%S";
    static constexpr int MAX_LEN = 30;

    timeval tv;  // local time
    mutable string str;  // string representation

    friend bool operator==(const Date& lhs, const Date& rhs);
    friend bool operator<(const Date& lhs, const Date& rhs);
    friend bool operator<=(const Date& lhs, const Date& rhs);
    friend double operator-(const Date& lhs, const Date& rhs);
};

inline bool operator==(const Date& lhs, const Date& rhs) {
    return lhs.tv.tv_sec == rhs.tv.tv_sec && lhs.tv.tv_usec == rhs.tv.tv_usec;
}

inline bool operator!=(const Date& lhs, const Date& rhs) {
    return !(lhs == rhs);
}

inline bool operator<(const Date& lhs, const Date& rhs) {
    return lhs.tv.tv_sec < rhs.tv.tv_sec
           || (lhs.tv.tv_sec == rhs.tv.tv_sec && lhs.tv.tv_usec < rhs.tv.tv_usec);
}

inline bool operator<=(const Date& lhs, const Date& rhs) {
    return lhs.tv.tv_sec < rhs.tv.tv_sec
           || (lhs.tv.tv_sec == rhs.tv.tv_sec && lhs.tv.tv_usec <= rhs.tv.tv_usec);
}

inline bool operator>(const Date& lhs, const Date& rhs) {
    return rhs < lhs;
}

inline bool operator>=(const Date& lhs, const Date& rhs) {
    return rhs <= lhs;
}

inline double operator-(const Date& lhs, const Date& rhs) {
    return (lhs.tv.tv_sec - rhs.tv.tv_sec) + (lhs.tv.tv_usec - rhs.tv.tv_usec) * 1e-6;
}

}
